
#include <BStyle>
#include <BPainter>
#include <BScrollBar>
#include <BMouseEvent>
#include <BSizePolicy>
#include <BFont>

#include <BScrollArea>
#include "member_BScrollArea.h"

using namespace BWE;

#define member					(*(member_BScrollArea*)_ptr)
#define member_allocate()		_ptr = new member_BScrollArea(this)
#define member_release()		delete (member_BScrollArea*)_ptr

BScrollArea::BScrollArea(BWidget* parent) : BWidget(parent)
{
	member_allocate();

	BScrollBar* vScrollBar = new BScrollBar();
	vScrollBar->setStep(23);
	this->setVerticalScrollBar(vScrollBar);

	BScrollBar* hScrollBar = new BScrollBar();
	hScrollBar->setStep(23);
	this->setHorizontalScrollBar(hScrollBar);

}
BScrollArea::~BScrollArea()
{
	member_release();
}

void BScrollArea::setAreaAlign(Align areaAlign)
{
	if (member.areaAlign != areaAlign)
	{
		member.areaAlign = areaAlign;
		this->setArea(member.area);
	}
}
Align BScrollArea::areaAlign() const
{
	return member.areaAlign;
}

void BScrollArea::setAreaPolicy(Policy widthPolicy, Policy heightPolicy)
{
	setAreaPolicy(BSizePolicy(widthPolicy, heightPolicy));
}
void BScrollArea::setAreaPolicy(const BSizePolicy& areaPolicy)
{
	if (member.areaPolicy != areaPolicy)
	{
		bool widthDirty = member.areaPolicy.widthPolicy() != areaPolicy.widthPolicy() && member.hScrollPolicy == ScrollPolicy_Never;
		bool heightDirty = member.areaPolicy.heightPolicy() != areaPolicy.heightPolicy() && member.vScrollPolicy == ScrollPolicy_Never;
		member.areaPolicy = areaPolicy;
		if (member.applyAreaPolicy())
		{
			member.adjustScrollBar();
			this->scrollArea();
		}
		if (widthDirty || heightDirty)
		{
			this->adjust();
		}
	}
}
const BSizePolicy& BScrollArea::areaPolicy() const
{
	return member.areaPolicy;
}

void BScrollArea::setAreaMargin(int left, int top, int right, int bottom)
{
	setAreaMargin(BMargin(left, top, right, bottom));
}
void BScrollArea::setAreaMargin(const BMargin& margin)
{
	if (member.areaMargin != margin)
	{
		bool widthDirty = member.areaMargin.width() != margin.width() && member.hScrollPolicy == ScrollPolicy_Never;
		bool heightDirty = member.areaMargin.height() != margin.height() && member.vScrollPolicy == ScrollPolicy_Never;
		member.areaMargin = margin;
		if (member.applyAreaPolicy())
		{
			member.adjustScrollBar();
			this->scrollArea();
		}
		if (widthDirty || heightDirty)
		{
			this->adjust();
		}
	}
}
const BMargin& BScrollArea::areaMargin() const
{
	return member.areaMargin;
}

void BScrollArea::setAreaSize(int width, int height)
{
	BRect area = member.rawArea;
	area.setSize(width, height);
	this->setArea(area);
}
void BScrollArea::setAreaSize(const BSize& size)
{
	BRect area = member.rawArea;
	area.setSize(size);
	this->setArea(area);
}

void BScrollArea::setArea(int x, int y, int width, int height)
{
	setArea(BRect(x, y, width, height));
}
void BScrollArea::setArea(const BRect& rawArea)
{
	if (member.rawArea != rawArea)
	{
		bool widthDirty = member.rawArea.width() != rawArea.width() && member.hScrollPolicy == ScrollPolicy_Never;
		bool heightDirty = member.rawArea.height() != rawArea.height() && member.vScrollPolicy == ScrollPolicy_Never;
		member.rawArea = rawArea;
		if (member.applyAreaPolicy())
		{
			member.adjustScrollBar();
			this->scrollArea();
		}
		if (widthDirty || heightDirty)
		{
			this->adjust();
		}
	}
}
const BRect& BScrollArea::area() const
{
	return member.area;
}

BPoint BScrollArea::offset() const
{
	return member.area.pos();
}

void BScrollArea::setScrollPolicy(ScrollPolicy scrollPolicy)
{
	setVerticalScrollPolicy(scrollPolicy);
	setHorizontalScrollPolicy(scrollPolicy);
}
void BScrollArea::setScrollPolicy(ScrollPolicy hScrollPolicy, ScrollPolicy vScrollPolicy)
{
	setVerticalScrollPolicy(vScrollPolicy);
	setHorizontalScrollPolicy(hScrollPolicy);
}

void BScrollArea::setVerticalScrollPolicy(ScrollPolicy scrollBarPolicy)
{
	if (member.vScrollPolicy != scrollBarPolicy)
	{
		member.vScrollPolicy = scrollBarPolicy;
		if (member.vScrollPolicy == ScrollPolicy_Floating)
		{
			member.vScrollBar->hide();
			member.connect(member.vScrollBar, Signal_Leave, &member_BScrollArea::slotMouseLeaveVerticalScrollBar);
		}
		else
		{
			member.disconnect(member.vScrollBar, Signal_Leave);
		}
		this->fresh();
	}
}
ScrollPolicy BScrollArea::verticalScrollPolicy() const
{
	return member.vScrollPolicy;
}

void BScrollArea::setHorizontalScrollPolicy(ScrollPolicy scrollBarPolicy)
{
	if (member.hScrollPolicy != scrollBarPolicy)
	{
		member.hScrollPolicy = scrollBarPolicy;
		if (member.hScrollPolicy == ScrollPolicy_Floating)
		{
			member.hScrollBar->hide();
			member.connect(member.hScrollBar, Signal_Leave, &member_BScrollArea::slotMouseLeaveHorizontalScrollBar);
		}
		else
		{
			member.disconnect(member.hScrollBar, Signal_Leave);
		}
		this->fresh();
	}
}
ScrollPolicy BScrollArea::horizontalScrollPolicy() const
{
	return member.hScrollPolicy;
}

void BScrollArea::setVerticalScrollBar(BScrollBar* scrollBar)
{
	if (member.vScrollBar != scrollBar)
	{
		if (member.vScrollBar)
		{
			member.disconnect(member.vScrollBar);
			this->removeChild(member.vScrollBar);
		}
		member.vScrollBar = scrollBar;
		if (member.vScrollBar)
		{
			member.vstep = member.vScrollBar->step();
			member.vScrollBar->setParent(this);
			member.vScrollBar->setOrientation(Orientation_Vertical);
			member.connect(member.vScrollBar, Signal_ValueChanged, &member_BScrollArea::slotVerticalScrollValueChanged);
		}
	}
}
const BScrollBar* BScrollArea::verticalScrollBar() const
{
	return member.vScrollBar;
}
BScrollBar* BScrollArea::verticalScrollBar()
{
	return member.vScrollBar;
}

void BScrollArea::setHorizontalScrollBar(BScrollBar* scrollBar)
{
	if (member.hScrollBar != scrollBar)
	{
		if (member.hScrollBar)
		{
			member.disconnect(member.hScrollBar);
			this->removeChild(member.hScrollBar);
		}
		member.hScrollBar = scrollBar;
		if (member.hScrollBar)
		{
			member.hstep = member.hScrollBar->step();
			scrollBar->setParent(this);
			scrollBar->setOrientation(Orientation_Horizontal);
			member.connect(member.hScrollBar, Signal_ValueChanged, &member_BScrollArea::slotHorizontalScrollValueChanged);
		}
	}
}
const BScrollBar* BScrollArea::horizontalScrollBar() const
{
	return member.hScrollBar;
}
BScrollBar* BScrollArea::horizontalScrollBar()
{
	return member.hScrollBar;
}

bool BScrollArea::scrollToRect(const BRect& rect, Align align)
{
	BRect crect = this->centerRect();
	BRect area = member.area;
	if (align == Align_None)
	{
		BRect ret = crect.wrap(rect);
		area.move(ret.pos() - rect.pos());
	}
	else
	{
		BRect ret = crect.align(align, rect.size());
		area.move(ret.pos() - rect.pos());
	}
	if (member.area != area)
	{
		this->setArea(area);
		return true;
	}
	return false;
}
bool BScrollArea::scrollToPoint(const BPoint& point, Align align)
{
	if (member.area.contain(point))
	{
		BRect crect = this->centerRect();
		BRect area = member.area;
		BPoint ret = crect.align(align, point);
		area.setPos(ret);
		if (member.area != area)
		{
			this->setArea(area);
		}
		return true;
	}
	return member.area.contain(point);
}

BSize BScrollArea::sizeHint() const
{
	BSize csize(100, 100);
	if (member.hScrollPolicy == ScrollPolicy_Never)
		csize.width() = member.rawArea.width();
	if (member.vScrollPolicy == ScrollPolicy_Never)
		csize.height() = member.rawArea.height();
	BSize hsize = this->preferSize(csize);
	return hsize;
}

void BScrollArea::scrollArea()
{
	emit(Signal_AreaChanged, member.area);
}

void BScrollArea::resizeEvent(const BEvent& event)
{
	BWidget::resizeEvent(event);
	member.applyAreaPolicy();
	this->scrollArea();
	member.dirty = true;
}

void BScrollArea::styleEvent(const BEvent& event)
{
	BWidget::styleEvent(event);
	const BStyle* style = this->realStyle();

	const BValue& handleSizeValue = style->value(Value_Handle_Size);
	if (handleSizeValue.valid())
		member.handleSize = handleSizeValue;

	if (member.handleSize < 1)
	{
		const BFont* font = this->font();
		if (font)
			member.handleSize = font->size() / 2;
	}
	if (member.handleSize < 1)
		member.handleSize = 10;

	member.dirty = true;
}
void BScrollArea::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.adjustScrollBar();
		member.dirty = false;
	}
	BWidget::freshEvent(event);
}
void BScrollArea::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawBorder();
	painter.drawPerch();
	painter.drawMargin();

	BRect area = this->area();

	if (member.hScrollPolicy == ScrollPolicy_Floating && !member.hScrollBar->visible())
	{
		BRect rect = this->rect(Part_Bottom);
		float range = (float)area.width();
		int page = bMin(rect.width(), area.width());
		int x = (int)(rect.x() + rect.width() * (rect.x() - area.x()) / range);
		int w = (int)(rect.width() * page / range);
		painter.setColor(Color_Handle);
		painter.fillRect(x, rect.y(), w, rect.height());
	}
	if (member.vScrollPolicy == ScrollPolicy_Floating && !member.vScrollBar->visible())
	{
		BRect rect = this->rect(Part_Right);
		float range = (float)area.height();
		int page = bMin(rect.height(), area.height());
		int y = (int)(rect.height() * (rect.y() - area.y()) / range) + rect.y();
		int h = (int)(rect.height() * page / range);
		painter.setColor(Color_Handle);
		painter.fillRect(rect.x(), y, rect.width(), h);
	}
}

void BScrollArea::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BRect& crect = this->centerRect();

	if (member.hScrollPolicy == ScrollPolicy_Floating && crect.width() < member.area.width())
	{
		const BPoint& pos = mouseEvent.pos();
		const BSize& size = member.hScrollBar->size();
		BRect bottomRect = this->rect(Part_Bottom);
		bottomRect.top() = bottomRect.bottom() - size.height();
		if (bottomRect.contain(pos) && !member.hScrollBar->visible())
		{
			BRect rect = bottomRect.align(Align_Bottom, member.hScrollBar->size());
			member.hScrollBar->setRect(rect);
			member.hScrollBar->popup();
			this->fresh();
		}
	}

	if (member.vScrollPolicy == ScrollPolicy_Floating && crect.height() < member.area.height())
	{
		const BPoint& pos = mouseEvent.pos();
		const BSize& size = member.vScrollBar->size();
		BRect rightRect = this->rect(Part_Right);
		rightRect.left() = rightRect.right() - size.width();
		if (rightRect.contain(pos) && !member.vScrollBar->visible())
		{
			BRect rect = rightRect.align(Align_Right, member.vScrollBar->size());
			member.vScrollBar->setRect(rect);
			member.vScrollBar->popup();
			this->fresh();
		}
	}
	BWidget::mouseMoveEvent(mouseEvent);
}
bool BScrollArea::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	int wheel = mouseEvent.wheel();
	if (member.vScrollBar)
	{
		int value = member.vScrollBar->value();
		int step = member.vScrollBar->step();
		member.vScrollBar->setValue(value - step * wheel);
	}
	return true;
}
